package com.sunmouse.toolkit.util;

import android.os.Handler;
import android.os.Message;

import com.sunmouse.toollib.utils.LogUtils;
import com.sunmouse.toollib.utils.StringUtils;
import com.sunmouse.toollib.utils.ThreadPoolUtils;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

/**
 * Created by Administrator on 2017/9/25 0025.
 */
public class ChatUtil {
    public static final int NEW_MSG = 1;
    public static final int ERROR = 2;
//    public static final String SERVER = "180.76.128.229";
    public static final String SERVER = "192.168.1.98";
    public static final int PORT = 2222;
    public static final String CHARSET = "UTF-8";
    public static final int CONNET_TIMEOUT = 15 * 1000;

    private static ChatUtil mUtil;
    private HashMap<String, Socket> clientMap;
    private BufferedWriter bw;
    private BufferedReader br;

    private ChatHandler mHandler;
    private Message mMsg;
    private OnMessageListener mOnMessageListener;
    private Socket mSocket;
    public InetSocketAddress mAddress;
    private String name;

    /**
     * lock
     */
    private boolean isReadTaskRunning;


    private ChatUtil(String name) {
        this.clientMap = new HashMap<>();
        this.mHandler = new ChatHandler();
        this.mAddress = new InetSocketAddress(SERVER, PORT);
        this.name = name;
    }

    public static ChatUtil getInstance(String name) {
        if (mUtil == null) {
            mUtil = new ChatUtil(name);
        }
        return mUtil;
    }

    /**
     * launch server
     */
    @Deprecated
    public void launchServer(int port) {
        ThreadPoolUtils.execute(new ServerTask(port));

    }

    /**
     * launch client
     */
    public void launchClient(String server, int port) {
        if (mSocket == null) {
            ThreadPoolUtils.execute(new ClientTask(server, port));
        }
    }

    /**
     * launch client
     * case-1:mSocket is null and is not connected  then try to connect
     * <p/>
     * case-2: new an instance
     */
    public boolean connect() {
//        if (mSocket != null) {
//            if (!isReadTaskRunning) {
//                try {
//                    mSocket.close();
//                    mSocket.connect(mAddress);
//                    ThreadPoolUtils.execute(new ReadTask());
//                    return true;
//                } catch (IOException e) {
////                e.printStackTrace();
//                    sendAsyncError(e);
//                }
//            }
//        }
        if (mSocket == null) {
            ThreadPoolUtils.execute(new ClientTask(SERVER, PORT));
            return true;
        }
        return false;
    }

    /**
     * send message
     */
    public void sendMessage(String msg) {
        ThreadPoolUtils.execute(new SendTask(msg));
    }

    /**
     * msg listener
     *
     * @param mOnMessageListener
     */
    public void setOnMessageListener(OnMessageListener mOnMessageListener) {
        this.mOnMessageListener = mOnMessageListener;
    }


    /**
     * server task
     */
    @Deprecated
    private class ServerTask implements Runnable {
        private int port;

        public ServerTask(int port) {
            this.port = port;
        }

        @Override
        public void run() {
            try {
                ServerSocket mServer = new ServerSocket(port);

                LogUtils.d("--------Server opened..." + mServer.getInetAddress().getHostName());
                while (true) {
                    Socket s = mServer.accept();
                    LogUtils.d("--------" + s.getInetAddress().getHostName() + " join in...");
                    clientMap.put(s.getInetAddress().getHostAddress(), s);
                    BufferedReader br = getReadStream();
                    String msg = br != null ? br.readLine() : "";
                    if (StringUtils.isEmpty(msg)) continue;
                    //send to all clients
                    for (HashMap.Entry<String, Socket> entry : clientMap.entrySet()) {
                        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(entry.getValue().getOutputStream(), CHARSET));
                        bw.write(msg, 0, msg.length());
                        bw.flush();
                        bw.close();
                        LogUtils.d("------write sending msg...");
                    }
                    if (br != null) br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
                sendAsyncError(e);
            }
        }
    }

    /**
     * client task
     */
    private class ClientTask implements Runnable {
        private String server;
        private int port;

        public ClientTask(String server, int port) {
            this.server = server;
            this.port = port;
        }

        @Override
        public void run() {
            try {
                mSocket = new Socket(server, port);
                bw = getWriteStream();
                if (bw == null) return;
                bw.write(name);
                bw.newLine();
                bw.flush();
                ThreadPoolUtils.execute(new ReadTask());
//                bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
            } catch (IOException e) {
//                e.printStackTrace();
                sendAsyncError(e);
            }
        }
    }


    /**
     * read task
     */
    private class ReadTask implements Runnable {
        private String msg;

        public ReadTask() {
            isReadTaskRunning = true;
        }

        @Override
        public void run() {
            br = getReadStream();
            while (isClientLaunched() && isReadTaskRunning) {
                try {
                    msg = (br != null) ? br.readLine() : "";
                    LogUtils.d("-------reading-msg--" + msg + "  isConnected=" + mSocket.isConnected());
                    if (!StringUtils.isEmpty(msg)) {
                        sendAsyncMsg(msg);
                    } else {
                        /**
                         * if read msg is null,then sending a test signal to
                         * check whether the remote server is still opened
                         */
                        mSocket.sendUrgentData(16);
                    }
                } catch (Exception e) {
//                    e.printStackTrace();
                    sendAsyncError(e);
                    if (StringUtils.isEmpty(msg)) {
                        isReadTaskRunning = false;
                        disconnect();
                    }
                }

            }
        }
    }

    /**
     * clear socket
     */
    private void clearSocket() {
        if (mSocket != null) {
            try {
                mSocket.close();
                mSocket = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * send async message
     *
     * @param msg
     */
    private void sendAsyncMsg(String msg) {
        mMsg = Message.obtain();
        mMsg.what = NEW_MSG;
        mMsg.obj = msg;
        mHandler.sendMessage(mMsg);
    }

    /**
     * send async error
     *
     * @param e
     */
    private void sendAsyncError(Exception e) {
        mMsg = Message.obtain();
        mMsg.what = ERROR;
        mMsg.obj = e;
        mHandler.sendMessage(mMsg);
    }

    /**
     * @return read stream
     */
    private BufferedReader getReadStream() {
        if (mSocket == null || mSocket.isClosed()) return null;
        if (br != null) return br;
        try {
            return new BufferedReader(new InputStreamReader(mSocket.getInputStream(), CHARSET));
        } catch (IOException e) {
//            e.printStackTrace();
            sendAsyncError(e);
        }

        return null;
    }

    /**
     * return write stream
     */
    private BufferedWriter getWriteStream() {
        if (mSocket == null || mSocket.isClosed()) return null;
        if (bw != null) return bw;
        try {
            return new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream(), CHARSET));

        } catch (IOException e) {
//            e.printStackTrace();
            sendAsyncError(e);
        }
        return null;
    }

    /**
     * send msg task
     */
    private class SendTask implements Runnable {
        String msg;

        public SendTask(String msg) {
            this.msg = msg;
        }

        @Override
        public void run() {
            if (mSocket == null) {
                sendAsyncError(new Exception("client is null"));
                return;
            }
            try {
                if (bw == null) {
                    bw = getWriteStream();
                }
                if (StringUtils.isEmpty(msg)) return;
                bw.write(msg);
                bw.newLine();
                bw.flush();
//                bw.close();
                LogUtils.d("-------------send msg--");
            } catch (IOException e) {
//                e.printStackTrace();
                sendAsyncError(e);
            }
        }
    }

    /**
     * Chat handler
     */
    private class ChatHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case NEW_MSG:
                    if (mOnMessageListener != null) mOnMessageListener.onMessage((String) msg.obj);
                    break;
                case ERROR:
                    if (mOnMessageListener != null) mOnMessageListener.onError((Exception) msg.obj);
                    break;
            }
        }
    }

    /**
     * msg interface
     */
    public interface OnMessageListener {
        void onMessage(String msg);

        void onError(Exception e);
    }

    /**
     * check whether client is launched
     */
    public boolean isClientLaunched() {
        if (mSocket == null) return false;
        return true;
    }

    /**
     * disconnect
     */
    public boolean disconnect() {
        try {
            if (bw != null) {
                bw.close();
                bw = null;
            }
            if (br != null) {
                br.close();
                br = null;
            }
            if (mSocket != null) {
                mSocket.close();
                mSocket = null;

            }
            mUtil = null;
            LogUtils.v("--------client disconnect success---");
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            LogUtils.v("--------client shut down failed---");
        }
        return false;
    }

    /**
     * generate a random name
     */
    public static String generateName() {
        String src = "abcdefjhjklmnopqrstuvwxyz";
        int random = (int) (10000 * Math.random());
        char[] name = new char[4];
        name[0] = src.charAt(random / 1000);
        name[1] = src.charAt(random % 1000 / 100);
        name[2] = src.charAt(random % 100 / 10);
        name[3] = src.charAt(random % 10);
        String userId = String.valueOf(name[0]).toUpperCase() + String.valueOf(name).substring(1, 4);
        String orgId = "org-" + (int) ((10 * Math.random() + 5) / 5);
        return orgId + "#" + userId;
    }

    /**
     * check whether the client connected remote server
     */
    public boolean isConnected() {
        if (mSocket != null) {
            return mSocket.isConnected() && isReadTaskRunning;
        }
        return false;
    }

    /**
     * check whether the client bound remote server
     */
    public boolean isBound() {
        if (mSocket != null) {
            return mSocket.isBound();
        }
        return false;
    }

    /**
     * check whether the client closed
     */
    public boolean isClosed() {
        if (mSocket != null) {
            return mSocket.isClosed();
        }
        return false;
    }


    /**
     * check whether server is valid
     */
    public boolean isServerValid(Exception e) {
        String i = e.getMessage();
        if (i.equals(Error.ECONNRESET)) return false;
        if (i.equals(Error.EPIPE)) return false;
        return true;
    }


    /**
     * Error
     */
    public static class Error {
        public static final String ECONNRESET = "sendto failed: ECONNRESET (Connection reset by peer)";
        public static final String EPIPE = "sendto failed: EPIPE (Broken pipe)";

    }


}
